package client

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"longmen/server/config/global"
	"longmen/server/pkg/common/helper/const"
	"longmen/server/pkg/common/helper/encrypt"
	"longmen/server/pkg/common/helper/util"
	"longmen/server/pkg/common/payment"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
)

const (
	TRADE_WAIT       = "TRADE_WAIT"    //等待付款
	TRADE_CANCEL     = "TRADE_CANCEL"  //取消交易
	TRADE_SUCCESS    = "TRADE_SUCCESS" //交易成功,即已经付款
	TRADE_FAILED     = "TRADE_FAILED"  //交易失败
	TRADE_OTHER      = "TRADE_OTHER"   //其他
	TradeSignModeMD5 = "MD5"
	TradeSignModeRSA = "RSA_SHA256"
)

/*
 原力支付技术文档:
 http://doc.forcepay.cn/Developer/Document/Index.html
 您只需在网站或APP中加个支付链接就可以实现收款接入
 原力支付支持收款,退款,转账完整业务API接口
 一般情况下看如下两篇文档即可完成接入
 1.极简创建交易订单接口---创建一个新订单
 2.支付成功HTTP回调通知格式---支付完成时的回调数据格式
*/
type ForcePayClient struct {
	Host string
	MID  string
	Key  string
	//custom params
	TradeProduct string
	/*
	   必填,最大长度500,字符串格式,大小写敏感.
	   产品的标识,标识了该交易订单的对应产品.
	   允许在一个订单中购买多种产品,可以包含多种产品标识进行合并付款,使用','号分割每个订单标识
	   例如:P10000001,P1000002,...最大为50个产品标识
	   更多产品标识可以在控制台<获得产品支付入口>中查看.
	*/
	TradePayMethod string
	/*
	   必填,字符串格式
	   指定了此交易采用的支付方式,可用值如下:
	   SELECT--由用户选择支付方式
	   ALP--使用支付宝支付
	   WXP--使用微信支付
	   QQP--使用QQ钱包支付
	   YSP--使用云闪付支付
	   PPP--使用PAYPAL支付
	*/
	TradePayMode string
	/*
	   必填,字符串格式
	   指定了此交易的付款模式
	   AUTO--当TradePayMethod为SELECT<由用户选择支付方式>时必须指定此参数.
	   QR --通过线上二维码扫码付款(电脑网站场景)
	   H5W --通过H5外部拉起支付宝或微信付款(手机网站,APP场景)
	   H5N --通过H5在支付宝微信内部直接付款(公众号,小程序场景)
	   DMF --通过线下扫码付款
	   GW --通过网关付款,仅PAYPAL支付时有效
	*/
	MerchantPublicKeyMD5 string
	/*
	   MerchantPublicKeyMD5是商户的公钥,可以在商户管理界面中进行导出获得
	   计算[MerchantPublicKey]的MD5特征码得到[MerchantPublicKeyMD5]:
	   5536BE6945E94D0F5C6EBD2E3E78D980
	*/
}

func init() {
	err := payment.RegisterPay(_const.FORCE_PAY_CODE, initForcePayClient)
	if err != nil {
		global.Log.Error("msg", "register focepay error", "err", err)
		return
	}
}

func initForcePayClient(api payment.PayApi) (payment.PayClient, error) {
	fName := "forcePay.initForcePayClient"
	var client *ForcePayClient
	if err := json.Unmarshal([]byte(api.Param), &client); err != nil {
		return nil, fmt.Errorf("%s%s", fName, err.Error())
	}
	client.Host = api.ReqUri
	client.MID = api.Mid
	client.Key = api.Key

	return client, nil
}

// Sn 產生訂單編號
func (c *ForcePayClient) Sn() string {
	return ""
}

// Pay 支付流程
func (c *ForcePayClient) Pay(charge *payment.Charge) (*payment.PayInfo, error) {
	//fName := "forcePay.Pay"
	urlPath := c.Host + "/tradepayment"

	info := &payment.PayInfo{}
	info.Type = "FORM"

	values := url.Values{}
	values.Add("MerchantID", c.MID)
	values.Add("TradeProduct", c.TradeProduct)
	values.Add("TradePayMethod", c.TradePayMethod)
	values.Add("TradePayMode", c.TradePayMode)
	values.Add("TradeNo", charge.OrderSn)
	sv := c.sortSignParameter(values)
	signParam := strings.Join(sv, "&")
	sign := strings.ToLower(encrypt.Md516(signParam))
	query := signParam + "&sign=" + sign

	m := make(map[string]string)
	m["PayURL"] = urlPath + "?" + query
	info.Content = c.toHTML(m, charge.Mobile)
	return info, nil
}

// 入金 callback 簽名驗證
func (c *ForcePayClient) VerifySign(req *http.Request) (*payment.BaseResult, error) {
	fName := "forcePay.VerifySign"
	body, err := ioutil.ReadAll(req.Body)
	if err != nil {
		return nil, fmt.Errorf("%s ReadAll %s", fName, err.Error())
	}
	fmt.Printf("%s body :%s\n", fName, string(body))

	r := make(map[string]string)
	err = util.Json2Struct(string(body), &r)
	if err != nil {
		return nil, fmt.Errorf("%s body :%s err :%s", fName, string(body), err.Error())
	}
	delete(r, "TradeSignMode")
	delete(r, "TradeSignature")

	tradeSignMode, ok := r["TradeSignMode"]
	if !ok {
		return nil, fmt.Errorf("%s body :%s TradeSignMode not exist", fName, string(body))
	}
	if tradeSignMode != TradeSignModeMD5 {
		return nil, fmt.Errorf("%s body :%s TradeSignMode not eq MD5", fName, string(body))
	}

	sv := c.sortSignMap(r)
	signParam := strings.Join(sv, "&")
	sign := strings.ToUpper(encrypt.Md5(signParam))
	signC := encrypt.Md5(fmt.Sprintf("%s#%s", sign, strings.ToUpper(c.MerchantPublicKeyMD5)))

	signP, ok := r["TradeSignature"]
	if !ok {
		return nil, fmt.Errorf("%s body :%s TradeSignature not exist", fName, string(body))
	}
	// 忽略大小写的比较, 错误则返回
	if !strings.EqualFold(signC, signP) {
		return nil, fmt.Errorf("%s sign :%s signP :%s verify sign fail", fName, sign, signP)
	}

	info := &payment.BaseResult{}

	tradeStatus, ok := r["TradeStatus"]
	if !ok {
		return nil, fmt.Errorf("%s body :%s tradeStatus not exist", fName, string(body))
	}
	switch strings.ToUpper(tradeStatus) {
	case TRADE_WAIT:
		info.Status = 0 //Processing
	case TRADE_SUCCESS:
		info.Status = 1 //Success
	default:
		info.Status = 2 //Failed
	}

	tradeNo, ok := r["TradeNo"]
	if !ok {
		return nil, fmt.Errorf("%s body :%s tradeNo not exist", fName, string(body))
	}
	tradeAmount, ok := r["TradeAmount"]
	if !ok {
		return nil, fmt.Errorf("%s body :%s TradeAmount not exist", fName, string(body))
	}
	amount, err := strconv.ParseFloat(tradeAmount, 64)
	if err != nil {
		return nil, fmt.Errorf("%s body :%s amount not a float %s", fName, string(body), err.Error())
	}

	info.IsSucceed = true
	jsonVal, _ := json.Marshal(r)
	info.OrderSn = tradeNo
	info.Params = string(jsonVal)
	info.Money = amount
	return info, nil
}

// Notify callback 返回三方格式
func (c *ForcePayClient) Notify(br *payment.BaseResult) *payment.BaseNotify {
	return &payment.BaseNotify{
		Type: payment.STRING,
		Ret:  "OK",
	}
}

// Withdrawal 出金流程
func (c *ForcePayClient) Withdrawal(charge *payment.Charge, withdrawal *payment.Withdrawal) (*payment.BaseResult, error) {
	fName := "forcePay.Withdrawal"
	return nil, fmt.Errorf("%s not support", fName)
}

// WithdrawalSign 出金 callback 簽名驗證
func (c *ForcePayClient) WithdrawalSign(r *http.Request) (*payment.BaseResult, error) {
	fName := "forcePay.WithdrawalSign"
	return nil, fmt.Errorf("%s not support", fName)
}

func (this *ForcePayClient) sortSignParameter(uv url.Values) []string {
	var params []string
	for k := range uv {
		params = append(params, k)
	}

	sort.Strings(params)
	for i, v := range params {
		params[i] = v + "=" + uv.Get(v)
	}
	return params
}

func (this *ForcePayClient) sortSignMap(m map[string]string) []string {
	var params []string
	for k := range m {
		params = append(params, k)
	}

	sort.Strings(params)
	for i, v := range params {
		params[i] = v + "=" + m[v]
	}
	return params
}

func (this *ForcePayClient) toHTML(m map[string]string, mobile bool) string {
	var blank = ""
	formatStr := `<form name="pay" action="%s" method = "GET" enctype="application/x-www-form-urlencoded" %s>`
	if mobile {
		blank = `target="_blank"`
		formatStr += `<button type='submit' class='v-btn v-btn--block v-btn--large v-btn--depressed v-btn--round theme--dark primary'>确认</button>`
	}
	formatStr += `</form>`

	html := fmt.Sprintf(formatStr, m["PayURL"], blank)

	if mobile {
		return html
	}

	return fmt.Sprintf(payment.FormTpl, html)
}

//http://doc.forcepay.cn/Developer/Document/TradeHttpNotifyDoc.html
type ForcePayCallBack struct {
	MerchantID     string //商户的标识,标识了该交易订单的所属商家. 更多商户标识可以在控制台<获得产品支付入口>中查看.
	SubMerchantID  string //子级商户的标识,标识了发起交易的所属子级商户,返回ROOT表示这是由主商户发起的交易. 更多商户标识可以在控制台<获得产品支付入口>中查看.
	TradeProduct   string //产品的标识,标识了该交易订单的对应产品. 如果是多产品合并支付的交易,则会返回多个产品标识,用","号分割 更多产品标识可以在控制台<获得产品支付入口>中查看.
	TradeQuantity  string //产品的数量,指示了该交易订单的对应产品购买数量. 如果是多产品合并支付的交易,则会返回多个产品数量,用","号分割
	TradeNo        string //此交易的订单号
	TradeName      string //此交易的名称,UTF8+URLEncode编码
	TradePromotion string //此交易的优惠,UTF8+URLEncode编码
	TradeAmount    string //指示了该交易订单的总金额,您总应该验证此交易金额是否正确无误,避免资损.
	TradeBeginTime string //指示了该交易的发起时间
	TradeEndinTime string //指示了该交易的完成时间
	TradeStatus    string
	/*
	   指示了该交易的状态,可能值如下:
	   TRADE_WAIT --等待付款
	   TRADE_CANCEL --取消交易
	   TRADE_SUCCESS --交易成功,即已经付款
	   TRADE_FAILED --交易失败
	   TRADE_OTHER --其他
	*/
	TradeGuestMobile string //买家的手机号码,用于在交易时通过发送短信通知买家结果. 这个参数实际上是在创建交易时候传入的,如果未传则为空
	TradeToken       string //如果交易成功,则返回此交易的凭据,这个凭据可在交易退款业务中使用 此凭据即支付宝微信的交易流水单号
	TradeBuyer       string
	/*
	   买家的唯一标识
	   这个唯一标识将在转账或分账操作中被使用.
	   如有需要您应该维护此唯一标识与用户的对应关系
	   *可以创建一个0.1元的支付链接,当客户支付后即可获取自己唯一标识.
	   支付宝的唯一标识是2088开头的,微信的唯一标识是28位字符串
	*/
	TradeCustomParam string
	/*
	   交易的自定义数据,在这个参数中保存着您的业务参数.
	   如果您设置了交易完成时使用HTTP联动通知,则此参数会在
	   HTTP联动通知回调中进行回传.
	*/
	TradeTimestamp string //交易通知的时间戳
	TradeSignMode  string
	/*
	   此交易通知的签名模式,支持的签名模式有如下:
	   NULL --无签名
	   MD5 --使用MD5进行简单签名
	   RSA_SHA256 --使用基于RSA非对称加密的SHA256签名算法,非常安全
	*/
	TradeSignature string
	/*
	   此交易通知的签名数据体.签名数据体是用商户的私钥对通知数据进行签名的,
	   只有商户对应的公钥才能验签通过,具体验签方法请看交易MD5验签算法
	   交易RSA_SHA256验签算法
	*/
}
